package binchunk

import (
	"encoding/binary"
	"math"
)

type reader struct {
	data []byte
}

func (this *reader) readByte() byte {
	b := this.data[0]
	this.data = this.data[1:]
	return b
}

func (this *reader) readUInt32() uint32 {
	u := binary.LittleEndian.Uint32(this.data)
	this.data = this.data[4:]
	return u
}

func (this *reader) readUInt64() uint64 {
	u := binary.LittleEndian.Uint64(this.data)
	this.data = this.data[8:]
	return u
}

func (this *reader) readLuaInteger() int64 {
	return int64(this.readUInt64())
}

func (this *reader) readLuaNumber() float64 {
	return math.Float64frombits(this.readUInt64())
}

func (this *reader) readString() string {
	size := uint(this.readByte())
	if size == 0 {
		return ""
	}
	if size == 0xFF {
		size = uint(this.readUInt64())
	}
	return string(this.readBytes(uint32(size - 1)))
}

func (this *reader) readBytes(n uint32) []byte {
	bytes := this.data[:n]
	this.data = this.data[n:]
	return bytes
}

func (this *reader) checkHeader() {
	if string(this.readBytes(4)) != LUA_SIGNATURE {
		panic("header.signature wrong")
	}
	if this.readByte() != LUA_VERSION {
		panic("header.version wrong")
	}
	if this.readByte() != LUA_FORMAT {
		panic("header.format wrong")
	}
	if string(this.readBytes(6)) != LUAC_DATA {
		panic("header.luacData wrong")
	}
	//04
	if this.readByte() != CINT_SIZE {
		panic("header.cintSize wrong")
	}
	//08
	if this.readByte() != CSIZET_SIZE {
		panic("header.csize_t error")
	}
	//04
	if this.readByte() != INSTRUCTION_SIZE {
		panic("header.csize_t error")
	}
	//08
	if this.readByte() != LUA_INTEGER_SIZE {
		panic("header.csize_t error")
	}
	//08
	if this.readByte() != LUA_NUMBER_SIZE {
		panic("header.instructionSize error")
	}
	//0x7856
	if this.readLuaInteger() != LUAC_INT {
		panic("header.luacInt error")
	}
	//
	if this.readLuaNumber() != LUAC_NUM {
		panic("header.luacNum error")
	}
}

func (this *reader) readProto(parentSrc string) *Prototype {
	source := this.readString()
	//todo ??? why
	if source == "" {
		source = parentSrc
	}
	return &Prototype{
		Source:          source,
		LineDefined:     this.readUInt32(),
		LastLineDefined: this.readUInt32(),
		NumParams:       this.readByte(),
		IsVarArg:        this.readByte(),
		MaxStackSize:    this.readByte(),
		Code:            this.readCode(),
		Constants:       this.readConstants(),
		UpValues:        this.readUpvalues(),
		Protos:          this.readProtos(source),
		LineInfo:        this.readLineInfo(),
		LocVars:         this.readLocVars(),
		UpvalueNames:    this.readUpValueNames(),
	}
}
